<?php
/**
 * A collection of a single model class instances, stored in database.
 * By default, all stored instances belongs to the collection.
 * Acts as a lazy queryset. Implements Iterator interface.
 *
 * uses standard MySQL library actually
 * @todo implement iterator http://fr.php.net/manual/fr/language.oop5.iterations.php
 * @link http://webappkit.net
 * @package webappkit
 * @subpackage db
 * @author J.Ducastel <nospam0@ducastel.name>
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
 */
class DbCollection {

    /**
     * constructor
     */
    function DbCollection($class=null) {
        // setting stored class
        if (is_string($class))
            $this->class = $class;
        // setting default filters
    }

    /**
     * sets owner mapper's reference
     * @param DbMapper $mapper
     */
    function setMapper(&$mapper) {
        $this->mapper = &$mapper;
        return true;
    }

    // queryset / filtering methods

    /**
     * model-based filter
     * search for self string _filter{$key}($value) method which has to create SQL filter
     * @param string $key name of the model property to filter on
     * @param mixed $value value to filter on
     * @return DbCollection filtered instance
     */
    function filter($key, $value) {
        $method = '_filter'.$key;
        if (!method_exists($this,$method)) {
            // trigger_error("no $method method to filter this");
            return false;
        }
        if ($sql = $this->$method($value)) {
            // $this->sqlFilter($sql);
            $this->filters[$key] = $sql;
        }
        return $this;
    }

    /**
     * chains filters grouped in array
     * @param array $filters associative array
     * @return DbCollection filtered instance
     */
    function filters($filters) {
        foreach ($filters as $key => $value) {
            $this->filter($key, $value);
        }
        return $this;
    }

    /**
     * adds a SQL WHERE clause filter
     * @param string $sql
     */
    function sqlFilter($sql) {
        $this->filters[] = $sql;
    }

    /**
     * return sql query
     */
    function getSql() {
        return $this->_buildQuery();
    }

    /**
     * return rows count
     * @return int
     */
    function count() {
        return mysql_num_rows($this->_getResult());
    }

    /**
     * get all items as an array
     * @access public
     * @return array(object)
     */
    function & getList() {
        // get result
        $list = array();
        while ($item = $this->_buildItem())
            $list[] = $item;
        return $list;
    }

    /**
     * gets a page of items
     * @param int $index (starting at 1)
     * @param int $lines lines per page, default 20
     * @return array(object)
     */
    function getPage($index=1, $lines=20) {
        // checking index
        // $index = is_int($index)?$index:1;
        // getting result
        $result = $this->_getResult();
        if ($index>1)
            mysql_data_seek($result, ($index-1)*$lines);
        $list = array();
        for ($n=0; $n<$lines; $n++) {
            if (!$item = $this->_buildItem())
                break;
            $list[] = $item;
        }
        return $list;
    }

    /**
     * return pages count
     * @param int $lines lines per page
     * @return int
     */
    function countPages($lines=20) {
        return ceil($this->count() / $lines);
    }

    // iterator methods

    /**
     * return current (first ) item in collection
     * @return object
     */
    function current() {
        if (!$this->current) {
            $this->current = $this->_buildItem();
        }
        return $this->current;
    }

    /**
     * return key of the current element (or 0)
     * @return scalar
     */
    function key() {

    }

    /**
     * set cursor to next element
     * @return bool
     */
    function next() {

    }/**/

    /**
     * tells if there is a next element in collection
     * @return bool
     */
    function valid() {

    }

    /**
     * resets cursor to first element
     * @return bool
     */
    function rewind() {

    }/**/

    // properties

    /**
     * @var string class of the collection instances
     * @access protected
     */
    var $class;

    /**
     * @var string partial SQL query, SELECT+FROM (excluding WHERE etc)
     * @access protected
     */
    var $space;

    /**
     * @var array WHERE blocks (to be chained by AND)
     */
    var $filters=array();

    /**
     * @var string ORDER BY clause
     */
    var $order;

    /**
     * @var resource query result as from mysql_query
     */
    var $result;

    /**
     * @var object current item
     */
    var $current;

    /**
     * @var array items already retrieved
     */
    // var $items=array();

    /**
     * @var DbMapper reference to owner DbMapper
     */
    var $mapper;

    // protected methods : instance generation etc

    /**
     * get query's result resource, execute if necessary
     * @access protected
     * @param bool $use_current if false, will execute query even if yet available
     * @return resource
     */
    function & _getResult($use_current=true) {
        if (!$this->result or !$use_current) {
            $sql = $this->_buildQuery();
            $this->result = mysql_query($sql);
            if (!$this->result) {
                trigger_error(mysql_error($sql));
                trigger_error($sql);
            }
        }
        return $this->result;
    }

    /**
     * builds SQL query from stored parts
     * @access protected
     * @return string
     */
    function _buildQuery() {
        $sql = $this->space
        .' WHERE 1';
        foreach ($this->filters as $filter)
            $sql .= ' AND ('.$filter.')';
        if ($this->order)
            $sql .= ' ORDER BY '.$this->order;
        $sql .= ' limit 1000';
        return $sql;
    }

    /**
     * builds a collection's item instance from current result
     * @uses $this->result
     * @return object|false
     */
    function _buildItem() {
        $result = $this->_getResult();
        // delegates to DbMapper if defined
        if ($this->mapper)
            return $this->mapper->buildObject($result, $this->class);
        // generic case
        return mysql_fetch_object($result, $this->class);
    }
}